package de.hsb.ai.agent.analyzer;

import game.Transport;

import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeSet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.hsb.ai.agent.analyzer.FugitivePathWeighterFactory.Weighter;
import de.hsb.ai.util.map.ScotlandJadeMap;
import de.hsb.ai.util.map.ScotlandJadeMapHelper.ScotlandJadeEdge;
import de.hsb.ai.util.map.ScotlandJadeMapHelper.ScotlandJadeVertex;
import de.hsb.ai.util.map.ScotlandJadeSubMap;

/**
 * FugitiveMap is a sub-map of a {@link ScotlandJadeMap} for
 * expanding possible fugitive paths.
 */
@SuppressWarnings("serial")
public class FugitiveMap extends ScotlandJadeSubMap {
	
	public enum TransportFilter {
		TAXI(Transport.TAXI),
		BUS(Transport.BUS),
		UG(Transport.UG),
		BLACK(Transport.BLACK);

		private final int transport;

		private TransportFilter(int transport) {
			this.transport = transport;
		}

		public boolean accepts(int transport) {
			return this.transport == Transport.BLACK ? true : this.transport == transport;
		}

		public static TransportFilter only(int transport) {
			for (TransportFilter filter : values()) {
				if (filter.transport == transport)
					return filter;
			}

			throw new NoSuchElementException("unknown transport: " + transport);
		}

	}
	
	private static final Logger logger = LoggerFactory.getLogger("sy.fuma");
	
	private final Comparator<FugitivePath> pathComparator = new Comparator<FugitivePath>() {

		@Override
		public int compare(FugitivePath p1, FugitivePath p2) {
			return p1.getWeight() < p2.getWeight() ? 1 : -1;
		}
		
	};

	private final static int MAX_DEPTH = 5;

	private final Set<FugitivePath> paths = new TreeSet<FugitivePath>(pathComparator);
	
	private final FugitivePathWeighterFactory weighterFactory;
	
	private int previousTransport = Transport.NONE;
	
	private int moveNumberOffset;
	
	private double WEIGHT_TRESHOLD = 0.4;

	public FugitiveMap(ScotlandJadeMap base,Set<ScotlandJadeVertex> vertexSubset, 
			FugitivePathWeighterFactory weighterFactory, 
			int moveNumberOffset, int previousTransport) {
		super(base, vertexSubset, Collections.<ScotlandJadeEdge>emptySet());
		this.weighterFactory = weighterFactory;
		this.moveNumberOffset = moveNumberOffset;
		this.previousTransport = previousTransport;
	}
	
	public void setWeightTreshold(double threshold) {
		WEIGHT_TRESHOLD = threshold;
	}
	
	public void expand() {
		expand(MAX_DEPTH);
	}

	public void expand(int depth) {
		expand(depth, TransportFilter.BLACK);
	}

	public void expand(int depth, TransportFilter filter) {
		synchronized (vertexSet()) {
			for (ScotlandJadeVertex sourceVertex : vertexSet()) {
				logger.debug("exanding source vertex: " + sourceVertex);
				FugitivePath path = new FugitivePath(getBase(), 
						sourceVertex, sourceVertex, 
						Collections.<ScotlandJadeEdge>emptyList(), 
						0D, moveNumberOffset, previousTransport);

				expand(sourceVertex, path, depth, filter);
			}
		}
	}

	public void expand(ScotlandJadeVertex sourceVertex, FugitivePath path, int depth, TransportFilter filter) {
		if (path.getEdgeList().size() == depth) {
			if (path.getWeight() < WEIGHT_TRESHOLD) return;
			paths.add(path);
			return;
		}
		
		Weighter weighter = weighterFactory.createWeighter(path);
		
		for (ScotlandJadeEdge edge : getBase().edgesOf(sourceVertex)) {
			if (!filter.accepts(edge.getType())) continue;

			ScotlandJadeVertex targetVertex = getBase().get(edge.getTargetIndex(sourceVertex.getIndex()));

			if (targetVertex.getType() != ScotlandJadeVertex.DEFAULT_TYPE) continue;

//			addVertex(targetVertex);
			
			double weight = 1;
			
			try {
				weight = weighter.getWeight(edge);
			} catch (Exception e) {
				logger.error("calculating weight for edge " + edge, e);
			}
			
			expand(targetVertex, path.expand(edge, weight), depth, filter);
		}
	}
	
	public void prune(int depth, TransportFilter filter) {
		logger.debug("pruning at " + depth + "(" + (depth - moveNumberOffset) + ")");
		depth = depth - moveNumberOffset;
		Iterator<FugitivePath> iter = paths.iterator();
		int pruned = 0;
		while (iter.hasNext()) {
			FugitivePath path = iter.next();
			if (depth >= path.getEdgeList().size()) continue;
			if (!filter.accepts(path.getEdgeList().get(depth).getType())) {
				iter.remove();
				++pruned;
			}
		}
		logger.info("pruned " + pruned);
	}
	
	public Set<FugitivePath> getPaths()	 {
		return paths;
	}

}
